/*
 * Copyright (C) 2003 Twink, spm7@waikato.ac.nz
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 *
 * Current version 2008-2011 (c)IRainman a.rainman@gmail.com
 */

#pragma once
#include "ServerSocket.h"
#include "SearchManager.h"
#include "Thread.h"

#ifdef _DEBUG
//#define _DEBUG_WEB_SERVER_
#endif;

const static uint64_t WEB_SERVER_USER_SESSION_TIMEOUT = 300;

class WebServerListener
{
	public:
		template<int I> struct X
		{
			enum { TYPE = I };
		};
		
		typedef X<0> Setup;
		typedef X<1> ShutdownPC;
		
		virtual void on(Setup) noexcept = 0;
		virtual void on(ShutdownPC, int action) noexcept = 0;
};

class WebServerManager : public Singleton<WebServerManager>, public ServerSocketListener, public Speaker<WebServerListener>, private SettingsManagerListener, private SearchManagerListener
{
	private:
#ifndef __INTEL_COMPILER
		friend Singleton<WebServerManager>;
#endif
		
		WebServerManager(void);
		~WebServerManager(void);
		
		void Stop();
		
		enum PageID
		{
			INDEX,
			DOWNLOAD_QUEUE,
			SEARCH,
			SETTINGS,
			LOGS,
			LOG,
			SYSLOG,
			DOWNLOAD_FINISHED,
			UPLOAD_QUEUE,
			UPLOAD_FINISHED,
			LOGOUT,
			PAGE_404
		};
		
		struct WebPageInfo
		{
			WebPageInfo(PageID n, string t) : id(n), title(t) {}
			PageID id;
			string title;
		};
		
		WebPageInfo *page404;
		
		typedef unordered_map<string, WebPageInfo*> WebPages;
		
		WebPages pages;
		
		string getDLQueue();
		string getULQueue();
		
		string getFinished(bool);
		string getSearch();
		string getSettings();
		
		string getLogsPage();
		string getLogs(bool Enable, const string& LogFile, const string& Caption, const string& MessageNotEnable);
		inline string getWebLogs();
		inline string getSysLogs();
		
		string search_delay;
		string results;
		string head;
		string foot;
		size_t PageIndex;
		bool sended_search;
		//bool search_started; // TODO
		
		map<size_t, string> SearchPage;
		
		bool started;
		CriticalSection cs;
		// ServerSocketListener
		void on(ServerSocketListener::IncomingConnection) noexcept;
		
		ServerSocket socket;
		
		struct user_login
		{
			uint64_t time;
			bool power;
		};
		
		map<string, user_login> LoggedIn;
		
		size_t row;
		
		std::string token;
		
	public:
		typedef struct
		{
			UserPtr User;
			string HubURL;
		} searchresult;
	private:
	
		map<size_t, searchresult> SearchResult;
		
	public:
		ServerSocket& getServerSocket()
		{
			return socket;
		}
		
		class UserStatus
		{
			private:
				bool isLogged, isPower;
			public:
				UserStatus(bool islogged = false, bool ispower = false) : isLogged(islogged), isPower(ispower)
				{
				
				}
				bool isloggedin()
				{
					return isLogged;
				}
				bool ispower()
				{
					return isPower;
				}
		};
		
		string getPage(const string& file, const string& IP, UserStatus CurrentUser);
		string getLoginPage();
		
		// SettingsManagerListener
		void on(SettingsManagerListener::Save, SimpleXML&) noexcept
		{
			if (BOOLSETTING(WEBSERVER))
			{
				Restart();
			}
			else
			{
				Stop();
			}
		}
		// SearchManagerListener
		void on(SearchManagerListener::SR, const SearchResultPtr& aResult) noexcept;
		
		void Start() noexcept;
		void Restart()
		{
			Stop();
			Start();
		}
		
		uint16_t getPort() const
		{
			return BOOLSETTING(WEBSERVER) && BOOLSETTING(WEBSERVER_ALLOW_UPNP) ? SETTING(WEBSERVER_PORT) : 0;
		}
		
		inline bool noPage(const string& for_find);
		
		StringList sClients;
		
		void login(const string& ip, bool PowerUser = false)
		{
			user_login tmp;
			tmp.power = PowerUser;
			tmp.time = GET_TICK();
			LoggedIn[ip] = tmp;
		}
		
		inline void search(string search_str, int search_type);
		
		/*void searchstarted(bool s) { // TODO
		    search_started = s;
		}*/
		
		void reset()
		{
			row = 0; /* Counter to permit FireFox correct item clicks */
			//search_started = false; // TODO
			SearchManager::getInstance()->removeListener(this);
		}
		
		UserStatus GetUserStatus(const string& ip)
		{
			map<string, user_login>::const_iterator i = LoggedIn.find(ip);
			if (i != LoggedIn.end())
			{
				const uint64_t elapsed = (GET_TICK() - i->second.time) / 1000;
				if (elapsed > WEB_SERVER_USER_SESSION_TIMEOUT)
				{
					LoggedIn.erase(i);
					return UserStatus();
				}
				else
					LoggedIn[ip].time = GET_TICK();
					
				return UserStatus(true, i->second.power);
			}
			
			return UserStatus();
		}
		
		//SearchResultList
		void AddSearchResult(size_t number, const UserPtr& User, const string& HubURL)
		{
			searchresult toAdd;
			toAdd.User = User;
			toAdd.HubURL = HubURL;
			SearchResult[number] = toAdd;
		}
		
		searchresult GetSearchResult(size_t number)
		{
			searchresult tmp;
			if (SearchResult.find(number) != SearchResult.end())
			{
				tmp = SearchResult[number];
			}
			return tmp;
		}
		//SearchPage
		inline void AddSearchPage(string rez, size_t num)
		{
			SearchPage[num] = rez;
		}
		
		string GetSearchPage(size_t num)
		{
			if (SearchPage.find(num) != SearchPage.end())
			{
				const string rez = SearchPage[num];
				SearchPage.erase(num);
				return rez;
			}
			return Util::emptyString;
		}
		
		size_t SearchPageNum()
		{
			return SearchPage.size();
		}
		
		void ChangePage(size_t num)
		{
			PageIndex = num;
		}
};

class WebServerSocket : public Thread
{
	public:
		void accept(ServerSocket *s)
		{
			int fromlen = sizeof(from);
			
#ifdef _DEBUG_WEB_SERVER_
			printf("accepting\n");
#endif
			sock = ::accept(s->getSock(), (struct sockaddr*) & from, &fromlen);
			u_long b = 1;
			ioctlsocket(sock, FIONBIO, &b);
		}
		
		StringMap getArgs(const string& arguments);
		
		int run();
		
		inline string getFile(string& Name);
		
	private:
		sockaddr_in from;
		SOCKET sock;
		HANDLE thread;
};
